import evdev
import re
import fcntl
import os
import time # 用于 poll 中的短暂延时

class TouchScreenHandler:
    def __init__(self, name_substring="wch.cn CH57x", screen_width=1024, screen_height=600):
        """
        初始化触摸屏处理器
        :param name_substring: 设备名字中的部分字符串
        :param screen_width: 显示屏宽度
        :param screen_height: 显示屏高度
        """
        self.name_substring = name_substring
        self.screen_width = screen_width
        self.screen_height = screen_height
        self.device_path = self.find_event_device()
        self.device = None
        self.touch_max_x = 1  # 默认值，后续自动检测
        self.touch_max_y = 1
        self.button_regions = []  # 按钮区域 (x1, y1, x2, y2)
        self.callbacks = {}       # 按钮回调 {index: function}
        self.drag_sensitive_indices = set() # 注册为拖动敏感的区域索引集合

        self.last_x = -1 # 最后一次触摸的屏幕坐标 X
        self.last_y = -1 # 最后一次触摸的屏幕坐标 Y

        self.is_touching = False # 是否有手指按在屏幕上
        self.pressed_region_index = -1 # 当前按下的区域索引，-1 表示没有按在任何注册区域

    def find_event_device(self):
        """根据设备名称查找 /dev/input 设备路径"""
        device_path = None
        try:
            with open("/proc/bus/input/devices", "r") as f:
                content = f.read()
            blocks = content.strip().split("\n\n")
            for block in blocks:
                if f'N: Name="{self.name_substring}' in block:
                    # 确保找到的是 eventX 设备
                    match = re.search(r'H: Handlers=.*?(event\d+)', block)
                    if match:
                        device_path = f"/dev/input/{match.group(1)}"
                        print(f"找到设备: {device_path}")
                        break
        except FileNotFoundError:
            print("/proc/bus/input/devices 文件未找到。无法查找输入设备。")
            device_path = None
        except Exception as e:
            print(f"读取 /proc/bus/input/devices 时发生错误: {e}")
            device_path = None

        if not device_path:
            print(f"未找到名称中包含 '{self.name_substring}' 的设备")
        return device_path

    def set_buttons(self, button_regions, callbacks=None, drag_sensitive_indices=None):
        """
        设置按钮区域、对应回调和拖动敏感区域
        :param button_regions: [(x1, y1, x2, y2), ...] (屏幕坐标)
        :param callbacks: {index: function}
        :param drag_sensitive_indices: list 或 set，包含 button_regions 中哪些索引是拖动敏感的 (如滑条)
        """
        self.button_regions = button_regions
        self.callbacks = callbacks if callbacks else {}
        self.drag_sensitive_indices = set(drag_sensitive_indices) if drag_sensitive_indices else set()

        # 简单检查回调是否都存在
        for idx in self.callbacks:
            if idx >= len(self.button_regions):
                 print(f"警告: 回调索引 {idx} 超出区域列表范围。")
            if not callable(self.callbacks[idx]):
                 print(f"警告: 索引 {idx} 对应的回调不是可调用对象。")
    def detect_touch_range(self):
        """自动检测触摸屏原始坐标的最大值"""
        if self.device is None:
            print("设备未初始化或打开失败，无法检测触摸范围")
            return
        try:
            abs_info = self.device.capabilities().get(evdev.ecodes.EV_ABS, [])
            found_x = False
            found_y = False
            if abs_info:
                for item in abs_info:
                    if isinstance(item, tuple) and len(item) == 2:
                        code, abs_params = item
                        if code == evdev.ecodes.ABS_MT_POSITION_X or code == evdev.ecodes.ABS_X: # 考虑 ABS_X 兼容性
                            self.touch_max_x = abs_params.max
                            found_x = True
                        elif code == evdev.ecodes.ABS_MT_POSITION_Y or code == evdev.ecodes.ABS_Y: # 考虑 ABS_Y 兼容性
                            self.touch_max_y = abs_params.max
                            found_y = True
                    if found_x and found_y:
                        break

            if not (found_x or found_y):
                 print("设备没有报告 ABS_MT_POSITION_X/Y 或 ABS_X/Y 能力")
                 # 保持默认值 1，避免除以零。可能导致缩放不准。

        except Exception as e:
            print(f"检测触摸范围时发生异常: {e}")
            # 发生异常时也保持默认值 1

        if found_x or found_y:
             print(f"检测到触摸原始坐标范围: X=0~{self.touch_max_x}, Y=0~{self.touch_max_y}")
        else:
             print("未能检测到触摸原始坐标范围，使用默认值 X=0~1, Y=0~1")


    def listen(self):
        """
        开始监听触摸事件 (阻塞模式)
        注意: 此方法会阻塞当前线程，通常用于简单的独立监听或调试。
        在复杂的应用中，非阻塞的 poll 方法配合主循环更合适。
        """
        if not self.device_path:
            print("未找到匹配设备，无法启动监听")
            return

        try:
            self.device = evdev.InputDevice(self.device_path)
            print(f"正在监听 {self.device_path} ...")
            self.detect_touch_range()

            raw_abs_x = 0 # 当前原始绝对触摸位置 X
            raw_abs_y = 0 # 当前原始绝对触摸位置 Y

            # # 尝试获取独占访问，需要 root 权限
            # try:
            #     self.device.grab()
            #     print("已获取设备独占访问权限。")
            # except PermissionError:
            #      print("没有权限获取设备独占访问权限，可能影响其他程序使用触摸屏。")
            # except OSError as e:
            #      print(f"获取设备独占访问权限时发生错误: {e}")


            for event in self.device.read_loop(): # read_loop 是阻塞的
                # print(f"事件: {event}") # 调试打印所有事件

                if event.type == evdev.ecodes.EV_ABS:
                    # 原始绝对坐标事件，用于更新当前触摸位置
                    if event.code == evdev.ecodes.ABS_MT_POSITION_X or event.code == evdev.ecodes.ABS_X:
                        raw_abs_x = event.value
                    elif event.code == evdev.ecodes.ABS_MT_POSITION_Y or event.code == evdev.ecodes.ABS_Y:
                        raw_abs_y = event.value


                elif event.type == evdev.ecodes.EV_KEY:
                    if event.value == 1:  # 按下事件 (如 BTN_TOUCH)
                        # 在按键按下时，根据最后一次报告的原始绝对坐标计算屏幕坐标
                        # 确保最大值不是零，避免除以零错误
                        scaled_x = int(raw_abs_x * self.screen_width / (self.touch_max_x if self.touch_max_x > 0 else 1))
                        scaled_y = int(raw_abs_y * self.screen_height / (self.touch_max_y if self.touch_max_y > 0 else 1))

                        self.last_x = scaled_x # 更新 last_x/y 供回调使用 (虽然理论上 EV_ABS 会先更新)
                        self.last_y = scaled_y

                        print(f"触摸屏幕坐标: ({scaled_x}, {scaled_y})")

                        # 检查是否在任何按钮区域内
                        found_region = False
                        for idx, (x1, y1, x2, y2) in enumerate(self.button_regions):
                            if x1 <= scaled_x <= x2 and y1 <= scaled_y <= y2:
                                print(f"触发了区域 {idx}")
                          
                                if idx in self.callbacks:
                                    try:
                                        self.callbacks[idx]()  # 调用回调函数
                                    except Exception as cb_e:
                                        print(f"调用区域 {idx} 的回调时发生异常: {cb_e}")
                                found_region = True
                                break # 找到匹配区域后退出检查

                    elif event.value == 0: # 释放事件
                        # print("触摸释放")
                        pass # 在 listen 模式下通常不需要在释放时做特定处理

                # elif event.type == evdev.ecodes.EV_SYN:
                #     pass # SYN 事件表示一组相关事件的结束

        except FileNotFoundError:
            print(f"设备文件 {self.device_path} 未找到。请检查设备是否连接和权限。")
        except PermissionError:
            print(f"没有权限访问设备文件 {self.device_path}。请检查用户权限（可能需要添加到 input 用户组或使用 sudo）。")
        except OSError as e:
             print(f"打开或读取设备 {self.device_path} 时发生操作系统错误: {e}")
        except KeyboardInterrupt:
            print("监听被用户中断，安全退出。")
        except Exception as e:
            print(f"监听过程中发生未知异常: {e}")
        finally:
            if self.device:
                 # 如果获取了独占访问，需要释放
                 try:
                     self.device.ungrab()
                     print("已释放设备独占访问权限。")
                 except Exception:
                      pass # 忽略释放失败的错误
                 self.device.close()
                 self.device = None
                 print(f"设备 {self.device_path} 已关闭。")

    def poll(self):
        """
        非阻塞读取一次可用触摸事件，并触发相应回调。
        """
        if not self.device_path:
            return

        if self.device is None:
            try:
                self.device = evdev.InputDevice(self.device_path)
                self.device.fn = True  # 设置非阻塞模式（你这里写错了，应该使用 fcntl 设置非阻塞）
                self.detect_touch_range()
                print(f"poll: 已打开设备 {self.device_path}，设置为非阻塞模式。")
            except Exception as e:
                print(f"poll: 打开设备 {self.device_path} 异常: {e}")
                self.device = None
                return

        try:
            events = list(self.device.read())
        except BlockingIOError:
            return
        except Exception as e:
            print(f"poll: 读取设备事件异常: {e}")
            return

        if not events:
            return

        # # 临时变量用于一组事件中只触发一次按键
        # triggered = False

        for ev in events:
            if ev.type == evdev.ecodes.EV_ABS:
                if ev.code == evdev.ecodes.ABS_MT_POSITION_X:
                    raw_abs_x = ev.value
                    self.last_x = int(raw_abs_x * self.screen_width / (self.touch_max_x or 1))
                elif ev.code == evdev.ecodes.ABS_MT_POSITION_Y:
                    raw_abs_y = ev.value
                    self.last_y = int(raw_abs_y * self.screen_height / (self.touch_max_y or 1))
                # print(f"poll: 更新 last_x={self.last_x}, last_y={self.last_y}")

            elif ev.type == evdev.ecodes.EV_KEY:
                if ev.value == 1 :
                    # # 只在第一次按下触发
                    # triggered = True
                    for idx, (x1, y1, x2, y2) in enumerate(self.button_regions):
                        if x1 <= self.last_x <= x2 and y1 <= self.last_y <= y2:
                            # print(f"poll: 触发按钮 {idx}")
                            if idx in self.callbacks:
                                try:
                                    self.callbacks[idx]()
                                except Exception as cb_e:
                                    print(f"poll: 回调按钮 {idx} 异常: {cb_e}")
                            break
                elif ev.value == 0:
                    # 释放时清除位置
                    self.last_x = -1
                    self.last_y = -1
                    # print("poll: 触摸释放，清除位置。")
    def close(self):
        """关闭触摸屏设备"""
        if self.device:
            try:

                try:
                     self.device.ungrab()
                     print("已释放设备独占访问权限。")
                except Exception:
                     pass # 忽略释放失败的错误

                self.device.close()
                print(f"设备 {self.device_path} 已关闭。")
            except Exception as e:
                print(f"关闭设备 {self.device_path} 时发生异常: {e}")
            self.device = None
            self.is_touching = False
            self.pressed_region_index = -1
            self.last_x = -1
            self.last_y = -1

# # 使用示例 (可以在单独的文件中导入并使用)
# if __name__ == "__main__":
#     def handle_power():
#         print("电源按钮点击!")

#     def handle_menu():
#         print("菜单按钮点击!")

#     def handle_volume_slide():
#         # 这个回调会在拖动时多次触发
#         print(f"音量滑条拖动到 X={touch_handler.last_x}")
#         # 在实际应用中，这里会根据 last_x 计算音量值并应用

#     screen_w = 1024
#     screen_h = 600

#     # 根据您的设备名称字符串初始化
#     touch_handler = TouchScreenHandler(name_substring="wch.cn CH57x", screen_width=screen_w, screen_height=screen_h)

#     # 定义区域 (屏幕坐标)
#     regions = [
#         (900, 0, 1024, 100), # 索引 0: 电源按钮
#         (0, 0, 100, 100),     # 索引 1: 菜单按钮
#         (200, 500, 800, 550) # 索引 2: 音量滑条
#     ]

#     # 定义回调
#     callbacks_map = {
#         0: handle_power,
#         1: handle_menu,
#         2: handle_volume_slide # 音量滑条回调
#     }

#     # 定义拖动敏感的索引
#     drag_sensitive_indices = [2] # 索引 2 (音量滑条) 是拖动敏感的

#     # 设置按钮、回调和拖动敏感区域
#     touch_handler.set_buttons(regions, callbacks_map, drag_sensitive_indices)

#     print("开始非阻塞轮询触摸事件 (按 Ctrl+C 停止)...")
#     try:
#         while True:
#             touch_handler.poll()
#             # 在非阻塞模式下，需要短暂延时，避免 CPU 占用过高
#             time.sleep(0.01)
#     except KeyboardInterrupt:
#         print("用户中断，停止轮询。")
#     except Exception as e:
#         print(f"轮询过程中发生异常: {e}")
#     finally:
#         touch_handler.close()
#         print("程序退出。")